package tech.xs.common.framework.util;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import lombok.SneakyThrows;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.http.ResponseEntity;
import tech.xs.common.auth.domain.cache.AuthTokenCache;
import tech.xs.common.auth.util.AuthTokenUtil;
import tech.xs.common.framework.domain.constant.Symbol;
import tech.xs.common.framework.domain.exception.BaseException;
import tech.xs.common.framework.domain.model.ApiResult;
import tech.xs.common.framework.holder.SpringContextHolder;
import tech.xs.common.sys.domain.constant.SysCacheKeyConstant;

import java.util.*;
import java.util.concurrent.TimeUnit;

public class RoleUtil {

    private static final Logger log = LoggerFactory.getLogger(RoleUtil.class);

    /**
     * 获取当前登陆Token的启用角色
     */
    public static Set<String> getCurrUserEnableRole() {
        AuthTokenCache token = AuthTokenUtil.getToken();
        return RoleUtil.getEnableRole(token.getRoleCodes());
    }

    public static Set<String> getEnableRole(Set<String> roles) {
        if (roles == null || roles.isEmpty()) {
            return new HashSet<>(0);
        }
        RedissonClient redissonClient = SpringContextHolder.getBean(RedissonClient.class);
        String cacheKey = SysCacheKeyConstant.SYS_ENABLE_ROLE_CACHE + RoleUtil.getRolesHashCode(roles);
        RLock lock = RoleUtil.getEnableRoleCacheReadLock();
        try {
            RBucket<Set<String>> bucket = redissonClient.getBucket(cacheKey);
            if (bucket.isExists()) {
                return bucket.get();
            }
        } catch (Exception e) {
            log.error("获取启用角色失败[缓存存在]", e);
            throw e;
        } finally {
            lock.unlock();
        }
        Environment environment = SpringContextHolder.getBean(Environment.class);
        ResponseEntity<ApiResult> response = DynamicRequestUtil.post(environment.getProperty("framework.service.sys.name"), "/role/loadEnableRole", JSONArray.toJSONString(roles), ApiResult.class);
        ApiResult<ArrayList<String>> apiResult = response.getBody();
        if (!apiResult.isSuccess()) {
            throw new BaseException("sys服务调用失败 apiResult:" + apiResult);
        }
        return new HashSet<>(apiResult.getData());
    }

    public static int getRolesHashCode(Set<String> roles) {
        List<String> roleList = new ArrayList<>(roles);
        Collections.sort(roleList);
        StringBuilder rolesStr = new StringBuilder();
        for (String item : roleList) {
            if (StrUtil.isBlank(item)) {
                continue;
            }
            rolesStr.append(item);
            rolesStr.append(Symbol.COLON);
        }
        return rolesStr.toString().hashCode();
    }

    @SneakyThrows
    public static RLock getEnableRoleCacheWriteLock() {
        RedissonClient redissonClient = SpringContextHolder.getBean(RedissonClient.class);
        // 加锁,过期时间一分钟
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(SysCacheKeyConstant.LOCK_ENABLE_ROLE_CACHE);
        RLock lock = readWriteLock.writeLock();
        if (lock.isLocked()) {
            if (!lock.tryLock(1, TimeUnit.MINUTES)) {
                throw new BaseException("加载启用角色限缓存失败[获取锁失败]");
            }
        }
        lock.lock(1, TimeUnit.MINUTES);
        return lock;
    }

    @SneakyThrows
    public static RLock getEnableRoleCacheReadLock() {
        RedissonClient redissonClient = SpringContextHolder.getBean(RedissonClient.class);
        // 加锁,过期时间一分钟
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(SysCacheKeyConstant.LOCK_ENABLE_ROLE_CACHE);
        RLock lock = readWriteLock.readLock();
        if (lock.isLocked()) {
            if (!lock.tryLock(1, TimeUnit.MINUTES)) {
                throw new BaseException("加载启用角色缓存失败[获取锁失败]");
            }
        }
        lock.lock(1, TimeUnit.MINUTES);
        return lock;
    }

}
